package org.rkuk.kasutil.media 
{
	import flash.events.NetStatusEvent;
	import flash.net.NetConnection;
	import flash.net.NetStream;
	import flash.net.NetStreamPlayOptions;
	
	/**
	 * @copy		org.rkuk.kasutil.media.MediaSourceEvent.READY
	 * @eventType	org.rkuk.kasutil.media.MediaSourceEvent.READY
	 */
	[Event(name = "ready", type = "org.rkuk.kasutil.media.MediaSourceEvent")]
	/**
	 * @copy		org.rkuk.kasutil.media.MediaSourceEvent.PLAY
	 * @eventType	org.rkuk.kasutil.media.MediaSourceEvent.PLAY
	 */
	[Event(name = "play", type = "org.rkuk.kasutil.media.MediaSourceEvent")]
	/**
	 * @copy		org.rkuk.kasutil.media.MediaSourceEvent.STOP
	 * @eventType	org.rkuk.kasutil.media.MediaSourceEvent.STOP
	 */
	[Event(name = "stop", type = "org.rkuk.kasutil.media.MediaSourceEvent")]
	/**
	 * @copy		org.rkuk.kasutil.media.MediaSourceEvent.PAUSE
	 * @eventType	org.rkuk.kasutil.media.MediaSourceEvent.PAUSE
	 */
	[Event(name = "pause", type = "org.rkuk.kasutil.media.MediaSourceEvent")]
	/**
	 * @copy		org.rkuk.kasutil.media.MediaSourceEvent.RESUME
	 * @eventType	org.rkuk.kasutil.media.MediaSourceEvent.RESUME
	 */
	[Event(name = "resume", type = "org.rkuk.kasutil.media.MediaSourceEvent")]
	/**
	 * @copy		org.rkuk.kasutil.media.MediaSourceEvent.SEEK
	 * @eventType	org.rkuk.kasutil.media.MediaSourceEvent.SEEK
	 */
	[Event(name = "seek", type = "org.rkuk.kasutil.media.MediaSourceEvent")]
	/**
	 * @copy		org.rkuk.kasutil.media.MediaSourceEvent.END
	 * @eventType	org.rkuk.kasutil.media.MediaSourceEvent.END
	 */
	[Event(name = "end", type = "org.rkuk.kasutil.media.MediaSourceEvent")]
	/**
	 * @copy		org.rkuk.kasutil.media.MediaSourceEvent.BUFFER
	 * @eventType	org.rkuk.kasutil.media.MediaSourceEvent.BUFFER
	 */
	[Event(name = "buffer", type = "org.rkuk.kasutil.media.MediaSourceEvent")]
	/**
	 * @copy		org.rkuk.kasutil.media.MediaSourceEvent.RESET
	 * @eventType	org.rkuk.kasutil.media.MediaSourceEvent.RESET
	 */
	[Event(name = "reset", type = "org.rkuk.kasutil.media.MediaSourceEvent")]
	
	/**
	 * 扩充了flash.net.NetStream的事件，可以更方便征听其播放状态的事件.
	 * 支持对来自FMS的视频流以及非FMS视频流的播放状态事件，但是不支持mp3流的事件。
	 * 由于MediaStream继承自flash.net.NetStream，所以MediaStream可以替代NetStream使用。
	 * 但是使用方法上稍有差别：
	 * @example	使用MediaStream时，首先征听ready事件，然后通过streamName属性设置其播放的视频流地址或名称，
	 * 当streamName属性被设置时，MediaStream会加载指定的视频流，当视频流可用时发出ready事件，
	 * 调用play方法播放视频流时，不需要再次指定视频流的地址或名称（这里是与flash.net.NetStream.play方法的区别）。
	 * <listing>
	 * var ms:MediaStream = new MediaStream(nc);
	 * ms.addEventListener(MediaSourceEvent.READY, onMediaSourceReady);
	 * 
	 * //在onMediaSourceReady函数中
	 * ms.play();//开始播放视频流，这里只可以指定播放起始时刻和播放长度，不能再指定视频流地址或名称
	 * </listing>
	 * @author rkuk
	 * @see	#play()
	 */
	public class MediaStream extends NetStream implements IMediaSource
	{
		private static const STATUS_PLAY:String = "NetStream.Buffer.Full";
		private static const STATUS_STOP:String = "NetStream.Play.Stop";
		private static const STATUS_PAUSE:String = "NetStream.Pause.Notify";
		private static const STATUS_BUFFER:String = "NetStream.Buffer.Empty";
		private static const STATUS_COMPLETE:String = "NetStream.Play.Complete";
		private static const STATUS_FLUSH:String = "NetStream.Buffer.Flush";
		private static const STATUS_SEEK:String = "NetStream.Seek.Notify";
		
		private var isLocal:Boolean;//是否是来自fms的NetConnection
		private var isAutoDownload:Boolean;
		private var isDisposed:int;//-1：没有dispose；0：正在dispose，1：完成dispose
		
		private var isInitialized:Boolean;//是否可用了，即duration，width，height以及各种操作可以执行
		private var isNeedReset:Boolean;//是否需要重置标志，当streamName改变时需要重置
		
		/**
		 * 表示MediaStream当前的播放状态，可能的状态有四种：
		 * STATUS_PLAY:正在播放
		 * STATUS_STOP:停止
		 * STATUS_PAUSE:暂停
		 * STATUS_BUFFER:正在缓冲
		 */
		private var streamStatus:String;
		
		private var isPlayCmdSuspending:Boolean;//play方法是否正在执行，但没执行成功
		private var isCloseCmdSuspending:Boolean;//close方法是否正在执行，但没执行成功
		private var isPauseCmdSuspending:Boolean;//pause方法是否正在执行，但没执行成功
		private var isResumeCmdSuspending:Boolean;//resume方法是否正在执行，但没执行成功
		private var isSeekCmdSuspending:Boolean;//seek方法是否正在执行，但没执行成功
		
		private var streamConnectionUri:String;
		private var streamUrlBuffer:String;//缓存新设置的streamName，以保证关闭之前的流后再重置为指定的streamName
		private var streamUrl:String;//当前MediaStream对象对应的streamName
		private var streamDuration:Number;//stream的总长度，即从头开始到末尾的时间长度，单位：秒
		private var streamTempDuration:Number;//来自fms的流，play时可以指定播放长度，如果指定的长度有效，则保存在此，单位：秒
		private var streamWidth:Number;//当前stream的宽度
		private var streamHeight:Number;//当前stream的高度
		private var streamClient:Object;//MediaStream的client，指定了client后，事件会调用client的方法来处理
		
		/**
		 * 创建MediaStream对象的构造函数
		 * @param	connection
		 * @param	autoDownload
		 * @param	peerID
		 */
		public function MediaStream(connection:NetConnection,autoDownload:Boolean=true, peerID:String = "connectToFMS") 
		{
			super(connection, peerID);
			isLocal = connection.uri == "null";
			isAutoDownload = isLocal && autoDownload;
			isDisposed = -1;
			streamConnectionUri = connection.uri;
		}
		
		public function get streamName():String
		{
			return streamUrl;
		}
		
		/**
		 * 获取和设定要播放的视频地址或名称.
		 * 使用FMS视频流时，设定按照文件类型设定流名称，使用非FMS视频流时，指定视频的地址。
		 * 设置此属性之后，会在视频流可用时发出ready事件。
		 */
		public function set streamName(value:String):void
		{
			if ((isDisposed == -1 && streamUrl != value && value != null) || isDisposed == 0)
			{
				if (isDisposed == 0)
					isDisposed = 1;
					
				streamUrlBuffer = value;
				isNeedReset = true;
				
				close();//此处会引起重置
			}
		}
		
		/**
		 * 当前视频流是否正在播放。
		 */
		public function get isPlaying():Boolean
		{
			return streamStatus == STATUS_PLAY;
		}
		
		/**
		 * 所使用的NetConnection的连接地址
		 */
		public function get connectionUri():String
		{
			return streamConnectionUri;
		}
		
		/**
		 * @inheritDoc
		 */
		public function dispose():void
		{
			isDisposed = 0;
			streamName = null;
		}
		
		private function resetStream():void
		{
			if (hasEventListener(NetStatusEvent.NET_STATUS))
				removeEventListener(NetStatusEvent.NET_STATUS, onNetStatus);
				
			isNeedReset = false;
			isInitialized = false;
			streamDuration = NaN;
			streamWidth = streamHeight = NaN;
			
			streamStatus = STATUS_STOP;
			isPlayCmdSuspending = false;
			isCloseCmdSuspending = false;
			isPauseCmdSuspending = false;
			isResumeCmdSuspending = false;
			isSeekCmdSuspending = false;
			
			streamUrl = streamUrlBuffer;
			
			if (isDisposed == 1)
				dispatchEvent(new MediaSourceEvent(MediaSourceEvent.DISPOSE));
			else
			{
				dispatchEvent(new MediaSourceEvent(MediaSourceEvent.RESET));
				
				if(streamUrl!=null)
					super.play(streamUrl);
			}
		}
		
		/**
		 * @private
		 */
		public function onMetaData(info:Object):void
		{
			if (!isInitialized)
			{
				isInitialized = true;
				if(isAutoDownload)
					super.pause();
				else
					super.close();
				
				streamDuration = info.duration;
				streamWidth = info.width;
				streamHeight = info.height;
				
				addEventListener(NetStatusEvent.NET_STATUS, onNetStatus);
				dispatchEvent(new MediaSourceEvent(MediaSourceEvent.READY));
			}
			else if (streamClient != null)
				streamClient.onMetaData(info);
		}
		
		/**
		 * @private
		 */
		public function onXMPData(info:Object):void
		{
			if (streamClient != null)
				streamClient.onXMPData(info);
		}
		
		/**
		 * 与play功能相同，提供IMediaSource接口的实现
		 * @see	#play()
		 */
		public function start(...rest):void
		{
			play.apply(this, rest);
		}
		
		/**
		 * 操作视频开始播放.
		 * MediaStream.play方法与NetStream.play方法参数有所不同，MediaStream.play方法不能指定视频的地址或名称，
		 * 只能指定播放的起始时刻可播放长度，分别是第一和第二个参数，MediaStream所播放的视频地址或名称通过streamName属性指定。
		 * @see	#streamName
		 * @see	flash.net.NetStream.play()
		 */
		override public function play(...rest):void 
		{
			if (isInitialized && !isPlayCmdSuspending)
			{
				stopTestAllCommands();
				
				if (!isLocal)//在fms上，暂停以后要play需要先close
					super.close();
					
				startTestPlay();
				
				var start:Number = 0;
				var len:Number = -1;
				
				if (!isLocal)//使用fms时，处理play方法的参数
				{
					if (rest.length > 0)
					{
						//第一个参数为start，即播放起始时刻，仅当设置的起始时刻在有效范围才赋值
						if (rest[0] > 0 && rest[0] <= streamDuration)
							start = rest[0];
							
						//第二个参数为length，即从start开始播放的时间长度，仅当设置的播放长度在有效范围才赋值
						if (rest.length > 1 && rest[1] >= 0)
						{
							len = rest[1];
							if (start + len > streamDuration)
							{//如果播放长度加上起始时间超过了总长度，则播放长度以总长减去起始时刻为准
								len = -1;
								streamTempDuration = streamDuration - start;
							}
							else
								streamTempDuration = len;
						}
						else
							streamTempDuration = NaN;
					}
					else//当play不指定参数时，以总长度为准，此处操作是为了消除上次播放如果设置了参数的影响
						streamTempDuration = NaN;
				}
				
				super.play(streamUrl, start, len);
			}
		}
		
		/**
		 * 操作视频开始播放，功能与flash.net.NetStream.play2方法类似.
		 * 但是MediaStream.play2方法只接受NetStreamPlayOptions中的start和len两个属性。
		 * @see flash.net.NetStream.play2()
		 */
		override public function play2(param:NetStreamPlayOptions):void 
		{
			play(param.start, param.len);
		}
		
		private function startTestPlay():void
		{
			isPlayCmdSuspending = true;
			
			setNetStatusListener(STATUS_PLAY, notifyPlay);
		}
		
		private function stopTestPlay():void
		{
			isPlayCmdSuspending = false;
			
			setNetStatusListener(STATUS_PLAY, null);
		}
		
		private function notifyPlay():void
		{
			isPlayCmdSuspending = false;
			streamStatus = STATUS_PLAY;
			
			startTestBuffer();
			startTestEnd();
			
			dispatchEvent(new MediaSourceEvent(MediaSourceEvent.PLAY));
		}
		
		/**
		 * 与close功能相同，提供IMediaSource接口
		 */
		public function stop():void
		{
			close();
		}
		
		/**
		 * @copy	org.rkuk.kasutil.media.IMediaSource#stop()
		 * @see		flash.net.NetStream.close()
		 */
		override public function close():void 
		{
			if (isInitialized && !isCloseCmdSuspending && (streamStatus!=STATUS_STOP || isPlayCmdSuspending))
			{
				stopTestAllCommands();
				
				if (isLocal)//不使用fms时，不会收到STATUS_STOP，因此要手动发出消息
				{
					super.close();
					notifyClose();
				}
				else
				{
					if (isNeedReset)//防止设置了streamName又立即play
						isInitialized = false;
						
					startTestClose();
					super.close();
				}
			}
			else if (isNeedReset && !isCloseCmdSuspending)//由于streamName变化，要重置
				resetStream();
		}
		
		/**
		 * 仅在isLocal=false时调用
		 */
		private function startTestClose():void
		{
			isCloseCmdSuspending = true;
			
			setNetStatusListener(STATUS_STOP, notifyClose);
		}
		
		private function stopTestClose():void
		{
			isCloseCmdSuspending = false;
			
			setNetStatusListener(STATUS_STOP, null);
		}
		
		private function notifyClose():void
		{
			isCloseCmdSuspending = false;
			streamStatus = STATUS_STOP;
			
			if (isNeedReset)//重置之前，禁止ClOSE事件征听器控制MediaStream
				isInitialized = false;
			
			dispatchEvent(new MediaSourceEvent(MediaSourceEvent.STOP));
			
			if (isNeedReset)//由于streamName变化，要重置
				resetStream();
		}
		
		/**
		 * @inheritDoc
		 * @see		flash.net.NetStream.resume()
		 */
		override public function resume():void 
		{
			//当play或stop命令没有执行成功时，不会执行resume
			//仅当在pause状态，且没有正在执行play或stop时，执行resume才有效
			if (isInitialized && !isResumeCmdSuspending && !isPlayCmdSuspending && !isCloseCmdSuspending 
				&& (streamStatus==STATUS_PAUSE || isPauseCmdSuspending))
			{
				if (isPauseCmdSuspending)
					stopTestPause();
				
				super.resume();
				startTestResume();
			}
		}
		
		private function startTestResume():void
		{
			isResumeCmdSuspending = true;
			//网速较快的时候，可能不会收到NetStream.Buffer.Full
			if (bufferLength >= bufferTime)
				notifyResume();
			else
				setNetStatusListener(STATUS_PLAY, notifyResume);
		}
		
		private function stopTestResume():void
		{
			isResumeCmdSuspending = false;
			setNetStatusListener(STATUS_PLAY, null);
		}
		
		private function notifyResume():void
		{
			isResumeCmdSuspending = false;
			streamStatus = STATUS_PLAY;
			
			startTestBuffer();
			startTestEnd();
			
			dispatchEvent(new MediaSourceEvent(MediaSourceEvent.RESUME));
		}
		
		/**
		 * @inheritDoc
		 * @see		flash.net.NetStream.pause()
		 */
		override public function pause():void 
		{
			if (isInitialized && !isPauseCmdSuspending && !isPlayCmdSuspending && !isCloseCmdSuspending
				&& (streamStatus==STATUS_PLAY || streamStatus==STATUS_BUFFER || isResumeCmdSuspending))
			{
				if (isResumeCmdSuspending)
					stopTestResume();
				if (streamStatus == STATUS_PLAY)
				{
					stopTestBuffer();
					stopTestEnd();
				}
				else if(streamStatus==STATUS_BUFFER)
					stopTestResume();
					
				if (isLocal)//不使用fms时，不会收到STATUS_PAUSE，因此要手动发出消息
				{
					super.pause();
					notityPause();
				}
				else
				{
					startTestPause();
					super.pause();
				}
			}
		}
		
		/**
		 * 仅在isLocal=false时调用
		 */
		private function startTestPause():void
		{
			isPauseCmdSuspending = true;
			setNetStatusListener(STATUS_PAUSE, notityPause);
		}
		
		private function stopTestPause():void
		{
			isPauseCmdSuspending = false;
			setNetStatusListener(STATUS_PAUSE, null);
		}
		
		private function notityPause():void
		{
			isPauseCmdSuspending = false;
			streamStatus = STATUS_PAUSE;
			
			dispatchEvent(new MediaSourceEvent(MediaSourceEvent.PAUSE));
		}
		
		/**
		 * @inheritDoc
		 * @see		flash.net.NetStream.seek()
		 */
		override public function seek(offset:Number):void 
		{
			if (isInitialized && !isPlayCmdSuspending && !isCloseCmdSuspending
				&& streamStatus!=STATUS_STOP && offset >= 0 && offset < streamDuration)
			{
				if (isSeekCmdSuspending)
					stopTestSeek();
					
				startTestSeek();
				super.seek(offset);
			}
		}
		
		private function startTestSeek():void
		{
			isSeekCmdSuspending = true;
			setNetStatusListener(STATUS_SEEK, notifySeek);
		}
		
		private function stopTestSeek():void
		{
			isSeekCmdSuspending = false;
			setNetStatusListener(STATUS_SEEK, null);
		}
		
		private function notifySeek():void
		{
			isSeekCmdSuspending = false;
			dispatchEvent(new MediaSourceEvent(MediaSourceEvent.SEEK));
		}
		
		private function startTestBuffer():void
		{
			setNetStatusListener(STATUS_BUFFER, notifyBuffer);
		}
		
		private function stopTestBuffer():void
		{
			setNetStatusListener(STATUS_BUFFER, null);
		}
		
		private function notifyBuffer():void
		{
			streamStatus = STATUS_BUFFER;
			dispatchEvent(new MediaSourceEvent(MediaSourceEvent.BUFFER));
			startTestResume();
		}
		
		private function startTestEnd():void
		{
			if (isLocal)
				setNetStatusListener(STATUS_STOP, notifyEnd);
		}
		
		private function stopTestEnd():void
		{
			if (isLocal)
				setNetStatusListener(STATUS_STOP, null);
		}
		
		/**
		 * @private
		 * @internal 当isLocal=false时，fms会通过onPlayStatus发出播放完成的消息
		 * @param	info
		 */
		public function onPlayStatus(info:Object):void
		{
			if (!isLocal && info.code == STATUS_COMPLETE)
				notifyEnd();
				
			if (streamClient != null)
				streamClient.onPlayStatus(info);
		}
		
		private function notifyEnd():void
		{
			stopTestAllCommands();
			streamStatus = STATUS_STOP;
			
			dispatchEvent(new MediaSourceEvent(MediaSourceEvent.END));
		}
		
		private function stopTestAllCommands():void
		{
			stopTestBuffer();
			stopTestClose();
			stopTestEnd();
			stopTestPause();
			stopTestPlay();
			stopTestResume();
			stopTestSeek();
		}
		
		private function setNetStatusListener(status:String, listener:Function):void
		{
			if (listener == null)
			{
				if(status in netStatusListeners)
					delete netStatusListeners[status];
			}
			else
				netStatusListeners[status] = listener;
		}
		
		private var netStatusListeners:Object = new Object();
		/**
		 * 用于征听NetStream命令执行的情况
		 * @param	e
		 * @see		flash.events.NetStatusEvent.info
		 */
		private function onNetStatus(e:NetStatusEvent):void
		{
			var status:String = e.info.code;
			if (status == STATUS_FLUSH)
				processFlush();
			else
			{
				var listener:Function = netStatusListeners[status] as Function;
				if (listener != null)
				{
					//先删除netStatusListeners中保存的listener引用，再执行listener
					//保证listener执行时新添加到netStatusListeners中的征听器不会被删除
					delete netStatusListeners[status];
					listener();
				}
			}
		}
		
		private function processFlush():void
		{
			if (netStatusListeners[STATUS_BUFFER] == notifyBuffer)
				setNetStatusListener(STATUS_BUFFER, startTestBuffer);
		}
		
		/**
		 * 所播放视频的宽度
		 */
		public function get width():Number
		{
			return streamWidth;
		}
		
		/**
		 * 所播放视频的高度
		 */
		public function get height():Number
		{
			return streamHeight;
		}
		
		/**
		 * 所播放视频的总时长，以秒为单位
		 */
		public function get duration():Number//根据play的参数来获取当前播放Stream的duration
		{
			if(isNaN(streamTempDuration))
				return streamDuration;
				
			return streamTempDuration;
		}
		
		/**
		 * 与flash.net.NetStream.client作用相同
		 * @see	flash.net.NetStream.client
		 */
		override public function get client():Object 
		{
			if (streamClient != null)
				return streamClient;
				
			return this;
		}
		
		override public function set client(value:Object):void 
		{
			if (value == this)
				streamClient = null;
			else
				streamClient = value;
		}
		
		/**
		 * @private
		 */
		public function onCuePoint(cuePoint:Object):void
		{
			if (streamClient != null)
				streamClient.onCuePoint(cuePoint);
		}
		
		/**
		 * @private
		 */
		public function onImageData(imageData:Object):void
		{
			if (streamClient != null)
				streamClient.onImageData(imageData);
		}
		
		/**
		 * @private
		 */
		public function onTextData(textData:Object):void
		{
			if (streamClient != null)
				streamClient.onTextData(textData);
		}
		
		public function get autoDownload():Boolean
		{
			return isAutoDownload;
		}
		
		/**
		 * 获取和设定是否自动加载视频流.
		 * true表示会在发出ready事件时候自动加载视频流，false表示在发出ready事件之后就不加载视频流，等到播放时才加载。
		 */
		public function set autoDownload(value:Boolean):void
		{
			isAutoDownload = isLocal && autoDownload;
		}
	}
}